package com.hwn.datax.controller;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.hwn.datax.component.SpringFXMLLoader;
import com.hwn.datax.domain.*;
import com.hwn.datax.domain.datax.Reader;
import com.hwn.datax.domain.datax.Writer;
import com.hwn.datax.event.CustomSpringEvent;
import com.hwn.datax.service.*;
import com.hwn.datax.utils.AlertUtil;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Modality;
import javafx.stage.Stage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Controller;

import java.io.IOException;
import java.util.*;

/**
 * @author wnhuang
 * @date 2024/5/31 17:56
 */
@Controller
public class JobBatchCreateController {

    @FXML
    public ProgressIndicator sourceDatabaseLoadingIndicator;

    @FXML
    public ProgressIndicator tableLoadingIndicator;
    @FXML
    public CheckBox truncateTable;

    @FXML
    private ComboBox<TreeItemNode<Long>> dataxGroupComboBox;

    @FXML
    private ComboBox<TreeItemNode<Integer>> dataxServerComboBox;

    @FXML
    private TextField channelTextField;

    @FXML
    private ComboBox<TreeItemNode<Long>> sourceDataSourceComboBox;

    @FXML
    private ComboBox<String> sourceDatabaseComboBox;

    @FXML
    private ComboBox<TreeItemNode<Long>> targetDataSourceComboBox;

    @FXML
    private ComboBox<String> targetDatabaseComboBox;

    @FXML
    private ListView<String> availableListView;

    @FXML
    private ListView<String> selectedListView;

    @Autowired
    RepositorySourceService repositorySourceService;

    @FXML
    private Button btnToRight;


    @FXML
    private Button btnToLeft;

    // Initial data for ListView
    ObservableList<String> leftItems = FXCollections.observableArrayList();
    ObservableList<String> rightItems = FXCollections.observableArrayList();

    ObservableList<String> targetItems = FXCollections.observableArrayList();

    @Autowired
    DataxJobService dataxJobService;

    List<RepositorySource> repositorySourceList;

    @Autowired
    ConfigService configService;

    @Autowired
    ServerInfoService serverInfoService;


    @Autowired
    private DataxJobGroupService dataxJobGroupService;

    @Autowired
    SpringFXMLLoader springFXMLLoader;

    @FXML
    public void initialize() {
        leftItems.clear();
        rightItems.clear();
        targetItems.clear();
        availableListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        selectedListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

        List<DataxJobGroup> list = dataxJobGroupService.list();

        dataxGroupComboBox.getItems().clear();
        dataxGroupComboBox.getItems().addAll(list.stream().map(t -> new TreeItemNode<>(t.getGroupName(), t.getId())).toList());


        List<ServerInfo> serverInfoList = serverInfoService.list();
        dataxServerComboBox.getItems().clear();
        dataxServerComboBox.getItems().addAll(serverInfoList.stream().map(t -> new TreeItemNode<Integer>(t.getServerName(), t.getId())).toList());

        repositorySourceList = repositorySourceService.list();
        List<TreeItemNode<Long>> collect = repositorySourceList.stream().map(t -> new TreeItemNode<Long>(t.getName(), t.getId())).toList();
        ObservableList<TreeItemNode<Long>> treeItemNodes = FXCollections.observableArrayList(collect);
        sourceDataSourceComboBox.getItems().addAll(collect);
        targetDataSourceComboBox.getItems().addAll(collect);
//        editor.textProperty().addListener((observable, oldValue, newValue) -> {
//            if (newValue == null || newValue.isEmpty()) {
//                sourceDataSourceComboBox.setItems(treeItemNodes);
//            } else {
//                treeItemNodes.setAll(collect.stream()
//                        .filter(item -> item.getLabel().toLowerCase().contains(newValue.toLowerCase()))
//                        .toList());
//                sourceDataSourceComboBox.show();
//            }
//        });
        availableListView.setItems(leftItems);
        selectedListView.setItems(rightItems);
        onDataSourceComboBoxChange(sourceDataSourceComboBox, sourceDatabaseComboBox, leftItems);
        onDataSourceComboBoxChange(targetDataSourceComboBox, targetDatabaseComboBox, targetItems);

// Button actions
        btnToRight.setOnAction(e -> {
            // 获取选中的项
            ObservableList<String> selectedItemList = availableListView.getSelectionModel().getSelectedItems();
            // 创建一个新的列表以存储要删除的项，以避免在遍历时修改列表
            ObservableList<String> itemsToRemove = FXCollections.observableArrayList(selectedItemList);

            if (selectedItemList != null && !selectedItemList.isEmpty()) {
                rightItems.addAll(selectedItemList);
                // 从数据源中移除选中的项
                leftItems.removeAll(itemsToRemove);
            }
        });

        btnToLeft.setOnAction(e -> {
            // 获取选中的项
            ObservableList<String> selectedItemList = selectedListView.getSelectionModel().getSelectedItems();
            // 创建一个新的列表以存储要删除的项，以避免在遍历时修改列表
            ObservableList<String> itemsToRemove = FXCollections.observableArrayList(selectedItemList);

            if (selectedItemList != null && !selectedItemList.isEmpty()) {
                leftItems.addAll(selectedItemList);
                rightItems.removeAll(itemsToRemove);
            }
        });


    }


    private void onDataSourceComboBoxChange(ComboBox<TreeItemNode<Long>> dataSourceComboBox, ComboBox<String> databaseComboBox, ObservableList<String> items) {
        dataSourceComboBox.setOnAction(event -> {
            sourceDatabaseLoadingIndicator.setVisible(true);
            Long repoId = dataSourceComboBox.getValue().getValue();
            Task<ObservableList<String>> task = getObservableSchemaListTask(repoId);
            task.setOnSucceeded(event1 -> {
                databaseComboBox.getItems().clear();
                ObservableList<String> value = task.getValue();
                databaseComboBox.getItems().addAll(value);
                sourceDatabaseLoadingIndicator.setVisible(false);
            });
            task.setOnFailed(event1 -> {
                // Handle any errors during data loading
                sourceDatabaseLoadingIndicator.setVisible(false);
                Alert alert = new Alert(Alert.AlertType.ERROR, "获取数据库失败！" + event1.getSource().getException().getMessage());
                alert.showAndWait();
            });
            // Start the task in a new thread
            new Thread(task).start();
        });

        if (items != null) {
            databaseComboBox.setOnAction(event -> {
                Long repoId = dataSourceComboBox.getValue().getValue();
                Task<ObservableList<String>> task = getObservableTableListTask(repoId, databaseComboBox.getValue());
                task.setOnSucceeded(event1 -> {
                    items.clear();
                    items.addAll(task.getValue());
                    tableLoadingIndicator.setVisible(false);
                });
                task.setOnFailed(event1 -> {
                    // Handle any errors during data loading
                    tableLoadingIndicator.setVisible(false);
                    Alert alert = new Alert(Alert.AlertType.ERROR, "获取表数据失败！" + event1.getSource().getException().getMessage());
                    alert.showAndWait();
                });
                tableLoadingIndicator.setVisible(true);
                rightItems.clear();
                new Thread(task).start();
            });
        }

    }


    private Task<ObservableList<String>> getObservableSchemaListTask(Long repoId) {
        return new Task<>() {
            @Override
            protected ObservableList<String> call() throws Exception {
                List<LinkedHashMap<String, Object>> schemas = repositorySourceService.getSchemas(String.valueOf(repoId));
                List<String> collect1 = schemas.stream().map(t -> Convert.toStr(t.get("schemaname"))).toList();
                return FXCollections.observableArrayList(collect1);
            }
        };
    }

    private Task<ObservableList<String>> getObservableTableListTask(Long repoId, String schemaName) {
        return new Task<>() {
            @Override
            protected ObservableList<String> call() throws Exception {
                List<LinkedHashMap<String, Object>> tableList = repositorySourceService.getTables(String.valueOf(repoId), schemaName);
                List<String> collect1 = tableList.stream().map(t -> Convert.toStr(t.get("tablename"))).toList();
                return FXCollections.observableArrayList(collect1);
            }
        };
    }

    private void loadComboBoxData() {

    }

    @FXML
    public void handleSave(ActionEvent actionEvent) {
        Map<String, String> map = matchAndRemove(rightItems, targetItems);
        if (map.isEmpty()) return;
        map.forEach((sourceTable, targetTable) -> {
            DataxJob dataxJob = JSONUtil.toBean(configService.getConfig("DefaultDataxTemplate"), DataxJob.class);
            Reader reader = dataxJob.getReader();
            Long sourceRepoId = sourceDataSourceComboBox.getValue().getValue();
            String sourceSchemaName = sourceDatabaseComboBox.getValue();

            reader.setType(0);
            reader.setRepoId(sourceRepoId);
            reader.setSchemaName(sourceSchemaName);
            reader.setTableName(sourceTable);

            Writer writer = dataxJob.getWriter();
            Long targetRepoId = targetDataSourceComboBox.getValue().getValue();
            String targetSchemaName = targetDatabaseComboBox.getValue();

            writer.setRepoId(targetRepoId);
            writer.setSchemaName(targetDatabaseComboBox.getValue());
            writer.setTableName(targetTable);
            writer.setTruncateTable(truncateTable.isSelected());

            List<LinkedHashMap<String, Object>> sourceColumns = repositorySourceService.getColumns(String.valueOf(sourceRepoId), sourceSchemaName, sourceTable);
            List<LinkedHashMap<String, Object>> targetColumns = repositorySourceService.getColumns(String.valueOf(targetRepoId), targetSchemaName, targetTable);
            List<String> sourceColumnName = sourceColumns.stream().map(t -> (String) t.get("columnname")).toList();
            List<String> targetColumnName = targetColumns.stream().map(t -> (String) t.get("columnname")).toList();

            reader.setAllColumns(sourceColumnName);
            writer.setAllColumns(targetColumnName);

            List<LinkedHashMap<String, Object>> primaryKeyList = sourceColumns.stream().filter(t -> t.get("primarykey") != null
                    && Convert.toInt(t.get("primarykey"), 0).equals(1)
                    && StrUtil.containsAnyIgnoreCase((String) t.get("datatype"), "NUMBER", "INT")).toList();
            if (!primaryKeyList.isEmpty()) {
                reader.setSplitPk((String) primaryKeyList.get(0).get("columnname"));
            }
            Map<String, String> matchColumn = matchAndRemove(sourceColumnName, targetColumnName);
            reader.setColumns(matchColumn.keySet().stream().toList());
            writer.setColumns(matchColumn.values().stream().toList());
            dataxJob.setJobName(sourceTable);

            dataxJob.setJobParams(JSONUtil.toJsonStr(buildJson(JSONUtil.parseObj(dataxJob)), 4));
            dataxJob.setGroupId(dataxGroupComboBox.getValue().getValue());
            dataxJob.setServerId(dataxServerComboBox.getValue().getValue());
            dataxJobService.save(dataxJob);
        });
        AlertUtil.showSuccessAlert("保存成功" + map.size());
        publishCustomEvent(String.valueOf(dataxGroupComboBox.getValue().getValue()));
    }

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void publishCustomEvent(final String message) {
        CustomSpringEvent customSpringEvent = new CustomSpringEvent(this, message);
        applicationEventPublisher.publishEvent(customSpringEvent);
    }

    public static Map<String, String> matchAndRemove(List<String> arr1, List<String> arr2) {
        Map<String, String> result = new HashMap<>();
        for (String item1 : arr1) {
            String left = item1;
            if (item1.indexOf('.') > -1) {
                left = item1.substring(item1.indexOf('.') + 1);
            }

            for (String item2 : arr2) {
                String right = item2;
                if (item2.indexOf('.') > -1) {
                    right = item2.substring(item2.indexOf('.') + 1);
                }

                if (left.equalsIgnoreCase(right)) {
                    result.put(item1, item2);
//                    iter1.remove();
//                    iter2.remove();
                    break;
                }
            }
        }
        return result;
    }

    private JSONObject buildJson(JSONObject job) {
        List<RepositorySource> repoList = repositorySourceList;
        JSONObject dataxPlugin = configService.getConfig("DataxPlugin");

        RepositorySource sourceRepository = repoList.stream().filter(t -> t.getId().equals(job.getJSONObject("reader").getLong("repoId"))).findFirst().get();
        RepositorySource targetRepository = repoList.stream().filter(t -> t.getId().equals(job.getJSONObject("writer").getLong("repoId"))).findFirst().get();

        JSONObject dataxJob = new JSONObject();
        dataxJob.set("setting", job.getJSONObject("setting"));

        JSONObject reader = new JSONObject();
        reader.set("name", dataxPlugin.getJSONObject(sourceRepository.getDbTypeName()).getStr("reader"));

        JSONObject readerParameter = new JSONObject();
        readerParameter.set("username", sourceRepository.getUsername());
        readerParameter.set("password", sourceRepository.getPassword());
        readerParameter.set("connection", Collections.singletonList(new JSONObject().set("jdbcUrl", Collections.singletonList(sourceRepository.getUrl()))));

        if (job.getJSONObject("reader").getInt("type") == 1) {
            readerParameter.getJSONArray("connection").getJSONObject(0).set("querySql", Collections.singletonList(job.getJSONObject("reader").getStr("querySql")));
        } else {
            readerParameter.getJSONArray("connection").getJSONObject(0).set("table", List.of(job.getJSONObject("reader").getStr("schemaName") + "." + job.getJSONObject("reader").getStr("tableName")));
            readerParameter.set("splitPk", job.getJSONObject("reader").getStr("splitPk"));
            readerParameter.set("column", job.getJSONObject("reader").getJSONArray("columns"));
        }

        reader.set("parameter", readerParameter);

        JSONObject writer = new JSONObject();
        writer.set("name", dataxPlugin.getJSONObject(targetRepository.getDbTypeName()).getStr("writer"));

        JSONObject writerParameter = new JSONObject();
        writerParameter.set("username", targetRepository.getUsername());
        writerParameter.set("password", targetRepository.getPassword());
        writerParameter.set("batchSize", job.getJSONObject("writer").getInt("batchSize"));
        writerParameter.set("connection", Collections.singletonList(new JSONObject().set("jdbcUrl", targetRepository.getUrl()).set("table", List.of(job.getJSONObject("writer").getStr("schemaName") + "." + job.getJSONObject("writer").getStr("tableName")))));
        writerParameter.set("column", job.getJSONObject("writer").getJSONArray("columns"));

        List<String> preSqlList = new ArrayList<>(Arrays.asList(job.getJSONObject("writer").getStr("preSql").split(";")));
        if (job.getJSONObject("writer").getBool("truncateTable")) {
            preSqlList.add("truncate table " + job.getJSONObject("writer").getStr("schemaName") + "." + job.getJSONObject("writer").getStr("tableName"));
        }
        writerParameter.set("preSql", preSqlList);

        writerParameter.set("postSql", Arrays.asList(job.getJSONObject("writer").getStr("postSql").split(";")));

        writer.set("parameter", writerParameter);

        List<JSONObject> content = new ArrayList<>();
        content.add(new JSONObject().set("reader", reader).set("writer", writer));

        dataxJob.set("content", content);

        return new JSONObject().set("job", dataxJob);
    }

    public void batchImport(ActionEvent actionEvent) {
        FXMLLoader loader = springFXMLLoader.getLoader("views/batch-import.fxml");
        Parent root;
        try {
            root = loader.load();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        BatchImportController controller = loader.getController();
        controller.init(leftItems, rightItems, targetItems);
        Stage stage = new Stage();
        stage.setTitle("快捷导入");
        stage.setScene(new Scene(root));
        // Make the popup modal
        stage.initModality(Modality.APPLICATION_MODAL);
        Platform.runLater(stage::showAndWait);
    }
}
